using System;
using System.Collections.Generic;
using UnityEngine;
using DanielSig.UnityExtensions;

namespace DanielSig.Geometry
{
	///<summary>
	///Most of the methods in this class are very slow, use them carefully.
	///</summary>
	public static class Geom
	{
		public const int DEFAULT_RESOLUTION = 512;
		public const int FAST_INVERSE_SQRT = 0x5f3759df;
		
		//just a method for fun
		public static Vector3 Cross(Vector3 a, Vector3 b)
		{
			Vector3 c = new Vector3();
			c.x = a.y * b.z - a.z * b.y;
			c.y = a.z * b.x - a.x * b.z;
			c.z = a.x * b.y - a.y * b.x;
			return c;
		}
		public static Vector3 NearestPointOnLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd, float radius)
		{
			Vector3 lineNormal = lineEnd - lineStart;
			lineNormal.Normalize();
			Vector3 pointOnLine = Vector3.Project(point, lineNormal);
			float dist = Vector3.Dot(pointOnLine, lineNormal);
			if(dist < 0F)
			{
				pointOnLine = lineStart;
			}
			else if(dist > Vector3.Dot(lineNormal, lineEnd - lineStart))
			{
				pointOnLine = lineEnd;
			}
			return pointOnLine + (point - pointOnLine).normalized * radius;
		}
		public static float DistanceToLine(Vector3 point, Vector3 lineStart, Vector3 lineEnd)
		{
			Vector3 lineNormal = lineEnd - lineStart;
			lineNormal.Normalize();
			Vector3 pointOnLine = Vector3.Project(point, lineNormal);
			float dist = Vector3.Dot(pointOnLine, lineNormal);
			if(dist < 0F)
			{
				pointOnLine = lineStart;
			}
			else if(dist > Vector3.Dot(lineNormal, lineEnd - lineStart))
			{
				pointOnLine = lineEnd;
			}
			return Vector3.Distance(point, pointOnLine);
		}
		public static float DistanceToInfinitateLine(Vector3 point, Vector3 pointOnLine1, Vector3 pointOnLine2)
		{
			return Vector3.Distance(point, Vector3.Project(point, (pointOnLine1 - pointOnLine2).normalized));
		}
		public static void RemoveFromUnsortedArray(ref Plane[] arr, int index)
		{
			if (arr.Length > 1) {
				arr[index] = arr[arr.Length - 1];
			}
			Array.Resize(ref arr, arr.Length - 1);
		}
		public static float PlaneIntersectionDistance(Plane plane1, Plane plane2)
		{
			float dot = Vector3.Dot(plane1.normal, plane2.normal);
			if (dot < 0) {
				return 0;
			}
			else if (dot == 1) {
				return float.PositiveInfinity;
			}
			float dist;
			plane2.Raycast(new Ray(plane1.normal * plane1.distance, Vector3.Cross(plane1.normal, Vector3.Cross(plane1.normal, plane2.normal))), out dist);
			return dist;
		}
		public static Vector3 Abs (Vector3 v)
		{
			return new Vector3 (v.x > 0 ? v.x : -v.x, v.y > 0 ? v.y : -v.y, v.z > 0 ? v.z : -v.z);
		}
		
		// a kind of a psuedo power method. VERY useful for making curvy normalized lines
		/*Intensify works like this
		     
		       0    0.5    1
		                   /
		                ..' 
		       ...--*'''          
		*/
		public static float Intensify(float number, float amount)
		{
			if(number >= 0F && number <= 1F){
				if(amount > 0F){
					amount = 1F / amount;
					return ((amount * amount + amount) / (1F + amount - number)) - amount;
				}
				Debug.LogError("Amount must be greater than 0");
			}
			Debug.LogError("Number must be normalized between 0 and 1");
			return float.NaN;
		}
		public static float Intensify(float number, float amount, float limit)
		{
			if(number >= 0F && number <= limit)
			{
				if(amount > 0F)
				{
					amount = limit / amount;
					return ((amount * amount + amount) / (limit + amount - number)) - amount;
				}
				Debug.LogError("Amount must be greater than 0");
			}
			Debug.LogError("Number must be normalized between 0 and " + limit + "(limit)");
			return float.NaN;
		}
		/*ClimbAndFall works like this
		     
		       0    0.5    1
		              ...
		          .-*'   '.        
		       .-'         \
		*/
		public static float ClimbAndFall (float number, float amount)
		{
			return number - Intensify (number, amount);
		}
		/*GlideAndStall works like this
		     
		       0    0.5    1
		       '*-.        /
		           '*-.  .'
		               ''          
		*/
		public static float GlideAndStall (float number, float amount)
		{
			return 1 + Intensify (number, amount) - number;
		}
		/*GlideAndFall works like this
		
		       0    0.5    1
		       '''''**--.
		                 '.
		                   \
		*/
		public static float GlideAndFall (float number, float amount)
		{
			return 1 - Intensify (number, amount);
		}
		/*JumpAndFly works a lot like square root, something like this
		
		       0    0.5    1
		           ..-**''''
		        .*'
		       /
		*/
		public static float JumpAndFly (float number, float amount)
		{
			return 1 - Intensify(1 - number, amount);
		}
		/*Sink works like this
		
		       0    0.5    1
		       \
		        '..
		           '**--....
		*/
		public static float Sink (float number, float amount)
		{
			return Intensify (1 - number, amount);
		}
		/*JumpAndGlide works like this
		     
		       0    0.5    1
		          ..
		        .'  '*-.        
		       /        '*-.
		*/
		public static float JumpAndGlide (float number, float amount)
		{
			return 2 - (number + Intensify (1 - number, amount))*2;
		}
		/*FallAndClimb works like this
		
		       0    0.5    1
		       \        .-*'
		        '.  ..-'
		          ''
		*/
		public static float FallAndClimb (float number, float amount)
		{
			return (number + Intensify(1 - number, amount));
		}
	
		
		
		
		
		#region creators
		public static Mesh CreateBox(float width, float height, float depth)
		{
			return CreateBox(new Vector3(width, height, depth));
		}
		public static Mesh CreateBox(Vector3 dimensions)
		{
			if(dimensions == Vector3.zero)
			{
				return null;
			}
			dimensions *= 0.5F;
			Vector3[] vertices = new Vector3[24];
			Vector2[] uvs = new Vector2[24];
			int[] triangles = new int[36];
			
			vertices[0]  = new Vector3(-dimensions.x,  dimensions.y, -dimensions.z);
			vertices[1]  = new Vector3( dimensions.x,  dimensions.y, -dimensions.z);
			vertices[2]  = new Vector3( dimensions.x, -dimensions.y, -dimensions.z);
			vertices[3]  = new Vector3(-dimensions.x, -dimensions.y, -dimensions.z);
			
			vertices[4]  = new Vector3( dimensions.x,  dimensions.y, -dimensions.z);
			vertices[5]  = new Vector3( dimensions.x,  dimensions.y,  dimensions.z);
			vertices[6]  = new Vector3( dimensions.x, -dimensions.y,  dimensions.z);
			vertices[7]  = new Vector3( dimensions.x, -dimensions.y, -dimensions.z);
			
			vertices[8]  = new Vector3( dimensions.x,  dimensions.y,  dimensions.z);
			vertices[9]  = new Vector3(-dimensions.x,  dimensions.y,  dimensions.z);
			vertices[10] = new Vector3(-dimensions.x, -dimensions.y,  dimensions.z);
			vertices[11] = new Vector3( dimensions.x, -dimensions.y,  dimensions.z);
			
			vertices[12] = new Vector3(-dimensions.x,  dimensions.y,  dimensions.z);
			vertices[13] = new Vector3(-dimensions.x,  dimensions.y, -dimensions.z);
			vertices[14] = new Vector3(-dimensions.x, -dimensions.y, -dimensions.z);
			vertices[15] = new Vector3(-dimensions.x, -dimensions.y,  dimensions.z);
			
			vertices[16] = new Vector3(-dimensions.x,  dimensions.y,  dimensions.z);
			vertices[17] = new Vector3( dimensions.x,  dimensions.y,  dimensions.z);
			vertices[18] = new Vector3( dimensions.x,  dimensions.y, -dimensions.z);
			vertices[19] = new Vector3(-dimensions.x,  dimensions.y, -dimensions.z);

			vertices[20] = new Vector3( dimensions.x, -dimensions.y,  dimensions.z);
			vertices[21] = new Vector3(-dimensions.x, -dimensions.y,  dimensions.z);
			vertices[22] = new Vector3(-dimensions.x, -dimensions.y, -dimensions.z);
			vertices[23] = new Vector3( dimensions.x, -dimensions.y, -dimensions.z);
			
			for(int uvIndex = 0, triIndex = 0; uvIndex < 24;)
			{
				triangles[triIndex++] = uvIndex;
				uvs[uvIndex++] = Vector2.zero;
				triangles[triIndex++] = uvIndex;
				uvs[uvIndex++] = Vector2.right;
				triangles[triIndex++] = uvIndex;
				triangles[triIndex++] = uvIndex;
				uvs[uvIndex++] = Vector2.one;
				triangles[triIndex++] = uvIndex;
				uvs[uvIndex++] = Vector2.up;
				triangles[triIndex++] = uvIndex - 4;
			}
			Mesh box = new Mesh();
			box.vertices = vertices;
			box.uv = uvs;
			box.triangles = triangles;
			return box;
		}
		#endregion
		#region private
		
		
		#endregion
	}
}